package com.zhangxun.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhangxun.domain.TimeOutDto;
import com.zhangxun.constant.QueueConstant;
import com.zhangxun.domain.*;
import com.zhangxun.feign.OrderCartFeign;
import com.zhangxun.feign.OrderMemberFeign;
import com.zhangxun.feign.OrderProductFeign;
import com.zhangxun.service.OrderItemService;
import com.zhangxun.service.OrderService;
import com.zhangxun.mapper.OrderMapper;
import com.zhangxun.util.IdUtils;
import com.zhangxun.util.SecurityUtil;
import com.zhangxun.vo.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 张循
 * @description 针对表【order(订单表)】的数据库操作Service实现
 * @createDate 2023-01-09 16:20:51
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
        implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemService OrderItemService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderCartFeign orderCartFeign;

    @Autowired
    private OrderProductFeign orderProductFeign;


    @Autowired
    private OrderMemberFeign orderMemberFeign;

    /**
     * 购物车结或者商品购买算跳转订单确认页面的方法，
     * 两种情况，从购物车条状有多个商品一起结算 basketIds集合保存了购物车商品id
     * 从商品详情页面跳转只有一个个商品 orderItem对象保存了商品的具体信息
     * 这两个都封装在orderConfirm对象里面
     *
     * @param orderConfirm 封装了商品信息的对象
     * @return
     */
    @Override
    public OrderVo confirm(OrderConfirm orderConfirm) {
        OrderVo orderVo = new OrderVo();
        //设置用户的默认收货地址
        UserAddr userAddr = orderMemberFeign.selectDefaultAddr(SecurityUtil.getWxUserId());
        orderVo.setUserAddr(userAddr);

        //获取两种方式传入的参数封装的对象，一个用户同一时间只能从一个地方进入订单确认页面。
        //所以两个队形肯定有一个为null.一个保存了数据
        OrderItem orderItem = orderConfirm.getOrderItem();
        List<Long> basketIds = orderConfirm.getBasketIds();

        //判断是用哪种方式进入订单详情页面
        if (ObjectUtil.isNotNull(orderItem)) {//从商品详情页面点击购买进入的订单详情页面
            //进行数据封装的具体方法
            productToOrder(orderItem, orderVo);

        } else {//从购物车页面进入的商品详情页面
            //进行数据封装的具体方法
            cartToOrder(basketIds, orderVo);
        }

        return orderVo;
    }

    /**
     * 订单提交保存订单的方法
     * 1，生成订单编号
     * 2，清空出购物车已经提交的商品
     * 3，扣减mysql库存
     * 4，扣减es库存
     * 5，保存订单信息，
     * 6，延迟队列加上死信队列删除订单回滚库存
     * 7，延迟队列加上死心队列提醒用户订单即将超时
     *
     * @param orderVo
     * @return
     */

    @Override
    public String submitOrder(OrderVo orderVo) {

        //1，通过hutool工具使用雪花算法生成订单号
        String orderNum = IdUtils.getId();


        //创建list集合保存宝物车商品id
        List<Long> basketIds = new LinkedList<>();


        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem -> {
                basketIds.add(orderItem.getBasketId());
            });
        });


        //2，清除购物车商品购物车
        deleteBasket(basketIds);

        //3，扣减mysql库存
        ChangeDto changeDto = updateCountInDb(orderVo);

        //4，扣减es库存
        updateCountInEs(changeDto.getProdChangeList());

        //5，保存订单信息
        saveOrder(orderNum, orderVo);

        //6，延迟队列取消订单回退库存
        timeOutFallBackCount(orderNum,changeDto);

        //7，延迟队列提醒用户订单即将过期
        timeOutMessage(orderNum,orderVo.getActualTotal());


        //8,返回订单编号
        return orderNum;
    }

    /**
     * 发送信息到消息延迟队列提醒用户订单即将过期
     * @param orderNum 订单号
     * @param totalPrice 订单商品总价
     */
    private void timeOutMessage(String orderNum,BigDecimal totalPrice) {

        //获取用户id查询用户电话号码
        String wxUserId = SecurityUtil.getWxUserId();
        User user = orderMemberFeign.selectPhoneNum(wxUserId);
        String userMobile = user.getUserMobile();
        String realName = user.getRealName();

        //使用对象封装参数进行消息的发送。
        MsgNoticeDto noticeDto = new MsgNoticeDto(orderNum, totalPrice, wxUserId, userMobile,realName);

        rabbitTemplate.convertAndSend(QueueConstant.LATE_MSG_QUEUE,JSON.toJSONString(noticeDto));


    }

    /**
     * 延迟队列，当订单15分钟没有支付就取消订单
     */
    private void timeOutFallBackCount(String orderNum, ChangeDto changeDto) {
        //创建一个对象封装数据
        TimeOutDto timeOutDto = new TimeOutDto(orderNum, changeDto);

        //发送消息到延迟队列
        rabbitTemplate.convertAndSend(QueueConstant.LATE_ORDER_QUEUE,JSON.toJSONString(timeOutDto));


    }




    /**
     * 保存订单数据的方法
     * @param orderNumber
     * @param orderVo
     */
    private void saveOrder(String orderNumber, OrderVo orderVo) {
        //订单主表对象
        Order order = new Order();
       StringBuilder builder = new StringBuilder();
        //list保存订单明细表对象
        List<OrderItem> itemList = new LinkedList<>();

        //给order赋值
        String userId = SecurityUtil.getWxUserId();
        order.setUserId(userId);
        order.setOrderNumber(orderNumber);
        order.setTotal(orderVo.getTotal());
        order.setActualTotal(orderVo.getActualTotal());
        order.setRemarks(orderVo.getRemarks());
        order.setAddrOrderId(order.getAddrOrderId());
        order.setProductNums(orderVo.getTotalCount());
        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem->{

                BigDecimal totalAmount = orderItem.getPrice().multiply(new BigDecimal(orderItem.getProdCount()));
                orderItem.setProductTotalAmount(totalAmount);
                //给orderItem赋值然后加入list
                orderItem.setRecTime(new Date());
                orderItem.setOrderNumber(orderNumber);
                itemList.add(orderItem);
                builder.append(orderItem.getProdName()).append(",");
            });
        });

        String prodNums = builder.toString();
        String finalName = builder.replace(prodNums.length()-1,prodNums.length(),"").toString();
        order.setProdName(finalName);
        order.setCancelTime(new Date());

        //保存order到订单主表
        orderMapper.insert(order);

        //保存到订单明细表
        OrderItemService.saveBatch(itemList);

    }

    /**
     * 修改es商品库存的方法。
     * 发送消息到修改es商品数量监听的队列。
     *
     * @param
     */
    private void updateCountInEs(List<ProdChange> prodChanges) {

        //将数据集合转换成json字符串
        String prodStr = JSON.toJSONString(prodChanges);

        //发送消息
        rabbitTemplate.convertAndSend(QueueConstant.ES_PROD_OPERATION_QUEUE, prodStr);
    }

    /**
     * 扣减数据库商品和sku库存的方法
     *
     * @param orderVo
     */
    private ChangeDto updateCountInDb(OrderVo orderVo) {

        //map封装商品的id和对应扣减的库存
        Map<Long, Integer> prodMap = new HashMap<>();

        //list封装保存skuid和扣减库存的数量
        List<SkuChange> skuChanges = new LinkedList<>();
        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem -> {
                SkuChange skuChange = new SkuChange(orderItem.getSkuId(), orderItem.getProdCount());
                //添加skuchange
                skuChanges.add(skuChange);
                Long prodId = orderItem.getProdId();
                if (prodMap.containsKey(prodId)) {
                    prodMap.put(prodId, prodMap.get(prodId) + orderItem.getProdCount());
                } else {
                    prodMap.put(prodId, orderItem.getProdCount());
                }

            });
        });

        List<ProdChange> prodChanges = new LinkedList<>();
        //将map转换成prodChange集合对象
        prodMap.entrySet().forEach(entry -> {
            ProdChange prodChange = new ProdChange(entry.getKey(), entry.getValue());
            prodChanges.add(prodChange);
        });

        //封装数据
        ChangeDto changeDto = new ChangeDto(prodChanges, skuChanges);

        //远程调用商品服务方法修改库存
        orderProductFeign.updateCountByIds(changeDto);

        return changeDto;
    }

    /**
     * 清除购物车商品的方法
     *
     * @param basketIds id集合
     */
    private void deleteBasket(List<Long> basketIds) {
        //判断集合是否有值
        if (CollectionUtil.isNotEmpty(basketIds)) {
            //有值，远程调用购物车服务清除相关数据
            orderCartFeign.deleteByIds(basketIds);
        }
    }

    /**
     * 购物车页面跳转封装数据的方法
     *
     * @param basketIds 购物车传入的参数的购物车的id集合
     * @param orderVo   封装返回的数据
     *                  同样的规则，满100-10块，最高减200.
     *                  满90免运费
     */
    private void cartToOrder(List<Long> basketIds, OrderVo orderVo) {
        //Y查询对应的购物车信息获取商品id和sku id
        List<Basket> basketList = orderCartFeign.getByCondition(basketIds);
        List<Long> prodIdList = basketList.stream().map(Basket::getProdId).collect(Collectors.toList());
        List<Long> skuIdList = basketList.stream().map(Basket::getSkuId).collect(Collectors.toList());

        //通过商品id和skuid查询对应的商品
        List<Prod> prodList = orderProductFeign.loadProdByIds(prodIdList);
        List<Sku> skuList = orderProductFeign.loadSkuByIds(skuIdList);

        //将商品和sku集合转换成map，方便后面查询
        Map<Long, Prod> prodMap = prodList.stream().collect(Collectors.toMap(Prod::getProdId, prod -> prod));
        Map<Long, Sku> skuMap = skuList.stream().collect(Collectors.toMap(Sku::getSkuId, sku -> sku));


        //进行商品信息的数据填充
        //将basketList通过店铺id分组，然后遍历进行数据的转移和封装
        Map<Long, List<Basket>> map = basketList.stream().collect(Collectors.groupingBy(Basket::getShopId));
        //集合用来保存每个basket的商品的数量，到最后计算商品总数量
        List<Integer> countList = new LinkedList<>();

        //集合用来保存每个商品的总价(单价乘以购买数量)
        List<BigDecimal> totalList = new LinkedList<>();

        //保存店铺集合
        List<ShopOrder> shopOrderList = new LinkedList<>();
        map.forEach((shopId, shopBasketList) -> {
            ShopOrder shopOrder = new ShopOrder();
            List<OrderItem> orderItemList = shopBasketList.stream().map(basket -> {

                OrderItem orderItem = new OrderItem();
                Prod prod = prodMap.get(basket.getProdId());
                Sku sku = skuMap.get(basket.getSkuId());
                BeanUtil.copyProperties(basket, orderItem);
                BeanUtil.copyProperties(prod, orderItem);
                BeanUtil.copyProperties(sku, orderItem);
                //计算商品总价
                totalList.add(sku.getPrice().multiply(new BigDecimal(basket.getBasketCount())));

                //商品计数
                countList.add(basket.getBasketCount());


                return orderItem;
            }).collect(Collectors.toList());

            shopOrder.setShopCartItemDiscounts(orderItemList);

            shopOrderList.add(shopOrder);
        });
        orderVo.setShopCartOrders(shopOrderList);

        //商品其他信息的数据填充
        //订单商品总数量
        Integer totalCount = countList.stream().reduce(Integer::sum).get();
        orderVo.setTotalCount(totalCount);
        //总金额
        BigDecimal total = totalList.stream().reduce(BigDecimal::add).get();
        orderVo.setTotal(total);
        //满减
        int num = (total.intValue() / 100) * 10 >= 200 ? 200 : (total.intValue() / 100) * 10;
        BigDecimal shopReduce = new BigDecimal(num);
        orderVo.setShopReduce(shopReduce);
        //运费
        BigDecimal Transfee = new BigDecimal(total.intValue() >= 90 ? 0 : 8);
        orderVo.setTransfee(Transfee);
        //实际金额
        orderVo.setActualTotal(total.subtract(shopReduce).add(Transfee));

    }

    /**
     * 从商品详情给页面进入订单页面的数据封装方法
     * 商具体信息需要对应的商品信息，sku信息。
     * 所以需要远程调用查询对应的商品信息和sku信息
     * 然后将数据封装进orderItem再封装进orderVo当中。
     * 还需要查询购物车表获取相应的对象封装
     * <p>
     * 还是同样的规则，慢100减10块，最奥减200.
     * 满90免运费
     *
     * @param orderItem
     * @param orderVo
     */
    private void productToOrder(OrderItem orderItem, OrderVo orderVo) {

        //远程调用实现查询商品信息和sku信息和购物车对应的数据的信息
        Prod prod = orderProductFeign.loadProdByIds(Arrays.asList(orderItem.getProdId())).get(0);
        Sku sku = orderProductFeign.loadSkuByIds(Arrays.asList(orderItem.getSkuId())).get(0);

        //给对象赋值
        BeanUtil.copyProperties(prod, orderItem);
        BeanUtil.copyProperties(sku, orderItem);

        //封装商品数据到vo当中
        List<ShopOrder> shopCarts = new LinkedList<>();
        ShopOrder shopOrder = new ShopOrder();
        List<OrderItem> orderItemList = new LinkedList<>();
        orderItemList.add(orderItem);
        shopOrder.setShopCartItemDiscounts(orderItemList);
        shopCarts.add(shopOrder);
        orderVo.setShopCartOrders(shopCarts);
        //封装其他数据到vo当中
        //设置商品总数
        orderVo.setTotalCount(orderItem.getProdCount());
        //设置商品总价
        BigDecimal totalMoney = sku.getPrice().multiply(new BigDecimal(orderItem.getProdCount()));
        orderVo.setTotal(totalMoney);
        //设置商品满减
        BigDecimal shopReduce = new BigDecimal((totalMoney.intValue() / 100) * 10 >= 200 ? 200 : totalMoney.intValue() / 10);
        orderVo.setShopReduce(shopReduce);
        //设置运费
        BigDecimal Transfee = new BigDecimal(totalMoney.intValue() >= 90 ? 0 : 8);
        orderVo.setTransfee(Transfee);
        //设置最终费用
        orderVo.setActualTotal(totalMoney.subtract(shopReduce).add(Transfee));

    }
}




